/**
 * @file kbd.c
 * @author lcz (you@domain.com)
 * @brief
 * @version 0.1
 * @date 2022-11-04
 *
 * @copyright Copyright (c) 2022
 *
 */

#include "dev/kbd.h"
#include "cpu/irq.h"
#include "comm/types.h"
#include "tools/log.h"
#include "comm/cpu_instr.h"
#include "tools/kilb.h"
#include "dev/tty.h"
/**
 * 键盘映射表，分3类
 * normal是没有shift键按下，或者没有numlock按下时默认的键值
 * func是按下shift或者numlock按下时的键值
 * esc是以esc开头的的键值
 * 功能键不用实现字符，不在这个表中
 */
static const key_map_t map_table[256] = {
    [0x2] = {'1', '!'},
    [0x3] = {'2', '@'},
    [0x4] = {'3', '#'},
    [0x5] = {'4', '$'},
    [0x6] = {'5', '%'},
    [0x7] = {'6', '^'},
    [0x08] = {'7', '&'},
    [0x09] = {'8', '*'},
    [0x0A] = {'9', '('},
    [0x0B] = {'0', ')'},
    [0x0C] = {'-', '_'},
    [0x0D] = {'=', '+'},
    [0x0E] = {ASCII_DEL, ASCII_DEL},
    [0x0F] = {'\t', '\t'},
    [0x10] = {'q', 'Q'},
    [0x11] = {'w', 'W'},
    [0x12] = {'e', 'E'},
    [0x13] = {'r', 'R'},
    [0x14] = {'t', 'T'},
    [0x15] = {'y', 'Y'},
    [0x16] = {'u', 'U'},
    [0x17] = {'i', 'I'},
    [0x18] = {'o', 'O'},
    [0x19] = {'p', 'P'},
    [0x1A] = {'[', '{'},
    [0x1B] = {']', '}'},
    [0x1C] = {'\n', '\n'},
    [0x1E] = {'a', 'A'},
    [0x1F] = {'s', 'B'},
    [0x20] = {'d', 'D'},
    [0x21] = {'f', 'F'},
    [0x22] = {'g', 'G'},
    [0x23] = {'h', 'H'},
    [0x24] = {'j', 'J'},
    [0x25] = {'k', 'K'},
    [0x26] = {'l', 'L'},
    [0x27] = {';', ':'},
    [0x28] = {'\'', '"'},
    [0x29] = {'`', '~'},
    [0x2B] = {'\\', '|'},
    [0x2C] = {'z', 'Z'},
    [0x2D] = {'x', 'X'},
    [0x2E] = {'c', 'C'},
    [0x2F] = {'v', 'V'},
    [0x30] = {'b', 'B'},
    [0x31] = {'n', 'N'},
    [0x32] = {'m', 'M'},
    [0x33] = {',', '<'},
    [0x34] = {'.', '>'},
    [0x35] = {'/', '?'},
    [0x39] = {' ', ' '},
};
// 键盘功能键的按下与否状态
static kbd_state_t kbd_state;

/**
 * @brief 处理切换tty设备
 * @param key 输入的字符
 */
static void do_fx_key(int key)
{
    // 获取索引
    int index = key - KEY_F1;
    if (kbd_state.lctrl_press || kbd_state.rctrl_press)
    {
        tty_select(index);
    }
}

/**
 * @brief 键盘初始化
 * @return * void
 */
void kbd_init(void)
{
    static int inited = 0;
    if (!inited)
    {
        kernel_memset(&kbd_state, 0, sizeof(kbd_state));
        irq_install(IRQ1_KEYBOARD, (irq_handler_t)exception_handler_kbd);
        irq_enable(IRQ1_KEYBOARD);
        inited = 1;
    }
}

/**
 * 等待可写数据
 */
void kbd_wait_send_ready(void)
{
    uint32_t time_out = 100000;
    while (time_out--)
    {
        if ((inb(KBD_PORT_STAT) & KBD_STAT_SEND_FULL) == 0)
        {
            return;
        }
    }
}

/**
 * 向键盘端口写数据
 */
void kbd_write(uint8_t port, uint8_t data)
{
    kbd_wait_send_ready();
    outb(port, data);
}

/**
 * 等待可用的键盘数据
 */
void kbd_wait_recv_ready(void)
{
    uint32_t time_out = 100000;
    while (time_out--)
    {
        if (inb(KBD_PORT_STAT) & KBD_STAT_RECV_READY)
        {
            return;
        }
    }
}

/**
 * 读键盘数据
 */
uint8_t kbd_read(void)
{
    kbd_wait_recv_ready();
    return inb(KBD_PORT_DATA);
}

/**
 * 更新键盘上状态指示灯
 */
static void update_led_status(void)
{
    int data = 0;

    data = (kbd_state.caps_lock ? 1 : 0) << 0;
    kbd_write(KBD_PORT_DATA, KBD_CMD_RW_LED);
    kbd_write(KBD_PORT_DATA, data);
    kbd_read();
}

/**
 * @brief 判断是否是按下键盘
 * @param key_code 扫描码
 * @return * int 弹开返回0，按下返回1
 */
static inline int is_make_code(uint8_t key_code)
{
    // 按键按下才处理，普通按键弹开我们不处理
    // 弹开我们直接返回,弹开返回0，按下返回1
    return !(key_code & 0x80);
}
/**
 * @brief 获取扫描码，低7位，最高位弃
 * @param key_code 扫描码
 * @return * int 低7位
 */
static inline int get_key(uint8_t key_code)
{
    return (key_code & 0x7f);
}
/**
 * @brief 处理普通字符，一字节字符
 * @param raw_code 扫描码
 */
static void do_normal_key(uint8_t raw_code)
{
    char key = get_key(raw_code); // 去掉最高位
    int is_make = is_make_code(raw_code);

    // 暂时只处理按键按下
    switch (key)
    {
        // shift, alt, ctrl键，记录标志位
    case KEY_RSHIFT:
        kbd_state.rshift_press = is_make; // 仅设置标志位
        break;
    case KEY_LSHIFT:
        kbd_state.lshift_press = is_make; // 仅设置标志位
        break;
    case KEY_CAPS:
        if (is_make)
        {
            kbd_state.caps_lock = ~kbd_state.caps_lock;
            update_led_status();
        }
    case KEY_ALT:
        kbd_state.lalt_press = is_make;
        break;
    case KEY_CTRL:
        kbd_state.lctrl_press = is_make;
        break;
    case KEY_F1:
    case KEY_F2:
    case KEY_F3:
    case KEY_F4:
    case KEY_F5:
    case KEY_F6:
    case KEY_F7:
    case KEY_F8:
        do_fx_key(key);
        break;
    case KEY_F9:
    case KEY_F10:
    case KEY_F11:
    case KEY_F12:
        break;
    default:
        if (is_make)
        {
            // 根据shift控制取相应的字符，这里有进行大小写转换或者shif转换
            if (kbd_state.rshift_press || kbd_state.lshift_press)
            {
                key = map_table[key].func; // 第2功能
            }
            else
            {
                key = map_table[key].normal; // 第1功能
            }

            if (kbd_state.caps_lock)
            {
                if ((key >= 'A') && (key <= 'Z'))
                {
                    key = key - 'A' + 'a';
                }
                else if ((key >= 'a') && (key <= 'z'))
                {
                    key = key - 'a' + 'A';
                }
            }

            // 最后，不管是否是控制字符，都会被写入
            // log_printf("key=%c", key);
            // 将字符放入tty的input队列中,默认放入0队列中
            tty_in(key);
        }
        break;
    }
}
/**
 * E0开始的键处理，只处理功能键，其它更长的序列不处理
 */
static void do_e0_key(uint8_t raw_code)
{
    // 同do_normal_key
    char key = get_key(raw_code); // 去掉最高位
    int is_make = is_make_code(raw_code);
    // E0开头，主要是HOME、END、光标移动等功能键
    // 设置一下光标位置，然后直接写入
    switch (key)
    {
    case KEY_ALT: // 右ctrl和左ctrl都是这个值
        kbd_state.ralt_press = is_make;
        break;
    case KEY_CTRL:
        kbd_state.rctrl_press = is_make;
        break;
    }
}

/**
 * @brief 处理键盘中断
 * @param frame
 * @return * void
 */
void do_handler_kbd(exception_frame_t *frame)
{
    static enum {
        NORMAL, // 普通，无e0或e1
        BEGIN_E0,
        BEGIN_E1,
    } recv_state = NORMAL;

    // 检查是否有数据，无数据则退出
    uint8_t status = inb(KBD_PORT_STAT);
    // 有数据为1,无数据为0
    if (!(status & KBD_STAT_RECV_READY))
    {
        pic_send_eoi(IRQ1_KEYBOARD);
        return;
    }

    uint8_t raw_code = inb(KBD_PORT_DATA);
    // do_normal_key(raw_code);
    // log_printf("key: %d", raw_code);
    pic_send_eoi(IRQ1_KEYBOARD);

    if (raw_code == KEY_E0)
    {
        recv_state = BEGIN_E0;
    }
    else if (raw_code == KEY_E1)
    {
        recv_state = BEGIN_E1;
    }
    else
    {
        // 扫描码是普通字符 再判断当前的状态是处于那种状态
        switch (recv_state)
        {
        case NORMAL:
            do_normal_key(raw_code);
            break;
        case BEGIN_E0:
            do_e0_key(raw_code);
            recv_state = NORMAL;
            break;
        case BEGIN_E1:
            // do_e1_key(raw_code);
            recv_state = NORMAL;
            break;
        default:
            break;
        }
    }
}